package com.haiyou.data.common.persister.mysql.help;

import java.io.File;
import java.io.FileReader;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.commons.lang3.Validate;
import org.springframework.util.ObjectUtils;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.haiyou.common.system.SystemProperties;
import com.haiyou.common.util.collect.CollectionUtils;
import com.haiyou.common.util.hash.HashUtils;
import com.haiyou.common.util.reflect.AnnotationFinder;
import com.haiyou.common.util.string.StringUtils;
import com.haiyou.data.common.persister.mysql.annotation.Database;
import com.haiyou.data.common.persister.mysql.dao.BaseDaoImpl;
import com.haiyou.data.common.persister.mysql.meta.DatabaseMeta;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import io.netty.util.internal.ObjectUtil;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;

/**
 * 使用HikariCP连接池 database options
 * 
 * @author Administrator
 *
 */
@Slf4j
public class DatabaseHelper {

	// {entityClass:meta}
	public static final Map<Class<?>, DatabaseMeta> databaseMetaPool = Maps.newHashMap();
	private static final Map<String, DatabaseMeta> _databaseMetaPool = Maps.newHashMap();

	// {alias:dbsource}分库的话会有多个
	public static final Map<String, DataSource> dataSourcePool = Maps.newHashMap();

	public static Connection openConnection(String dbName) {
		try {
			return dataSourcePool.get(dbName).getConnection();
		} catch (Exception e) {
			throw new RuntimeException(e.getMessage() + ":" + dbName, e);
		}
	}

	public static void closeConnection(ResultSet resultSet, Statement statement, Connection connection) {
		try {
			if (resultSet != null)
				resultSet.close();
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		try {
			if (statement != null)
				statement.close();
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		try {
			if (connection != null)
				connection.close();
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
	}

	public static DatabaseMeta getDatabaseMeta(Class<?> entityClass) {
		return databaseMetaPool.get(entityClass);
	}

	private static DatabaseMeta parseDatabaseMeta(Class<?> entityClass) {
		return databaseMetaPool.computeIfAbsent(entityClass, cls -> {
			Database database = AnnotationFinder.findAnnotation(entityClass, Database.class);
			DatabaseMeta databaseMeta = _databaseMetaPool.computeIfAbsent(database.alias(), k -> {
				DatabaseMeta meta = new DatabaseMeta();
				meta.setEntityClassName(entityClass.getName());
				meta.setAlias(database.alias());
				meta.setEngine(database.engine());
				meta.setCharset(database.charset());
				meta.setCollate(database.collate());
				meta.setCluster(database.cluster());
				Database.ConnectionPool connectionPool = (Database.ConnectionPool) SystemProperties
						.getEnum("db.connectionPool", Database.ConnectionPool.class, database.connectionPool());
				meta.setConnectionPool(connectionPool);
				log.trace("create DatabaseMeta [{}]", meta);
				return meta;
			});
			Validate.isTrue(database.cluster() == databaseMeta.getCluster(), "相同的库在不同的实体上设置了不同的分库策略[%s]", databaseMeta);
			return databaseMeta;
		});
	}

	public static DatabaseMeta fix(Class<?> entityClass) {
		Database database = AnnotationFinder.findAnnotation(entityClass, Database.class);

		java.util.Properties properties = MysqlConfig.getByDbType(database.alias());
		
		if(ObjectUtils.isEmpty(properties)) {
			log.warn("找不到数据库配置文件 {},{}",entityClass.getSimpleName(),database.alias());
			return null;
		}
		
		DatabaseMeta databaseMeta = parseDatabaseMeta(entityClass);

		String alias = database.alias();
		
		String urlKey = databaseMeta.getConnectionPool() == Database.ConnectionPool.DRUID
				|| databaseMeta.getConnectionPool() == Database.ConnectionPool.DBCP2 ? "url"
						: databaseMeta.getConnectionPool() == Database.ConnectionPool.HIKARI ? "jdbcUrl" : null;
		String[] ss = properties.getProperty(urlKey).split("\\?")[0].split("\\/");
		String reallyName = ss[ss.length - 1];
		
		databaseMeta.getReallyNames().add(reallyName);

		boolean auto = com.haiyou.common.system.Properties.database_auto_create();
		
		if (auto) {// 自动创库
			if (!dataSourcePool.containsKey(Database.HELPDB)) {// 使用mysql自带的数据库用于执行辅助操作，比如创建数据库等功能
				Properties helpProperties = new Properties();
				properties.forEach((k, v) -> helpProperties.put(k, v));
				helpProperties.put(urlKey, properties.getProperty(urlKey).replaceAll(reallyName, Database.HELPDB));
				try {
					DataSource helpDataSource = dataSourcePool.computeIfAbsent(Database.HELPDB, k -> {
						try {
							return (databaseMeta.getConnectionPool() == Database.ConnectionPool.HIKARI
									? new HikariDataSource(new HikariConfig(helpProperties))
									: databaseMeta.getConnectionPool() == Database.ConnectionPool.DRUID
											? DruidDataSourceFactory.createDataSource(helpProperties)
											: databaseMeta.getConnectionPool() == Database.ConnectionPool.DBCP2
													? BasicDataSourceFactory.createDataSource(properties)
													: null);
						} catch (Exception e) {
							throw new RuntimeException(e.getMessage(), e);
						}
					});
					Objects.requireNonNull(helpDataSource);
					log.trace("create datasource pool [{}.{}.{}]", Database.HELPDB,
							databaseMeta.getConnectionPool(), helpDataSource);
				} catch (Exception e) {
					throw new RuntimeException(e.getMessage(), e);
				}
			}
			if (!existsDatabase(reallyName)) {
				createDatabaseIfNotExists(reallyName, databaseMeta.getCharset(), databaseMeta.getCollate());
			}
		}

		try {
			DataSource dataSource = dataSourcePool.computeIfAbsent(alias, k -> {
				try {
					return (databaseMeta.getConnectionPool() == Database.ConnectionPool.HIKARI
							? new HikariDataSource(new HikariConfig(properties))
							: databaseMeta.getConnectionPool() == Database.ConnectionPool.DRUID
									? DruidDataSourceFactory.createDataSource(properties)
									: databaseMeta.getConnectionPool() == Database.ConnectionPool.DBCP2
											? BasicDataSourceFactory.createDataSource(properties)
											: null);
				} catch (Exception e) {
					throw new RuntimeException(e.getMessage(), e);
				}
			});
			Objects.requireNonNull(dataSource);
			dataSourcePool.computeIfAbsent(reallyName, k -> dataSource);
			log.trace("create datasource pool [{}.{}.{}]", alias, databaseMeta.getConnectionPool(), dataSource);
			log.trace("create datasource pool [{}.{}.{}]", reallyName, databaseMeta.getConnectionPool(),
					dataSource);
		} catch (Exception e) {
			throw new RuntimeException("初始化数据库连接池失败:" + entityClass.getSimpleName() + "," + database.alias(), e);
		}
		
		return getDatabaseMeta(entityClass);
	}
	
//	public static DatabaseMeta fix(Class<?> entityClass) {
//		Database database = AnnotationFinder.findAnnotation(entityClass, Database.class);
//		
//		String path = com.haiyou.common.system.Properties.mysql_config_path_ResourcesAbsolutePath();
//		File[] files = new File(path)
//				.listFiles(f -> f.getName().startsWith(database.alias()) && f.getName().endsWith(".properties"));
//		if (files == null || files.length < 1) {
//			log.warn("找不到数据库配置文件:" + entityClass + "," + path + "," + StringUtils.toString(new File(path).listFiles())
//					+ "," + database);
//			return null;
//			// throw new RuntimeException("找不到数据库配置文件:" + entityClass + "," + path);
//		}
//
//		DatabaseMeta databaseMeta = parseDatabaseMeta(entityClass);
//
//		for (int i = 0; i < files.length; i++) {
//			File file = files[i];
//			int lastIndex = file.getName().lastIndexOf('.');
//			String alias = file.getName().substring(0, lastIndex);
//			Properties properties = new Properties();
//
//			try {
//				@Cleanup
//				FileReader fileReader = new FileReader(file);
//				properties.load(fileReader);
//			} catch (Exception e) {
//				throw new RuntimeException(e.getMessage() + ":" + file, e);
//			}
//
//			String urlKey = databaseMeta.getConnectionPool() == Database.ConnectionPool.DRUID
//					|| databaseMeta.getConnectionPool() == Database.ConnectionPool.DBCP2 ? "url"
//							: databaseMeta.getConnectionPool() == Database.ConnectionPool.HIKARI ? "jdbcUrl" : null;
//			String[] ss = properties.getProperty(urlKey).split("\\?")[0].split("\\/");
//			String reallyName = ss[ss.length - 1];
//
//			for (int index = 0; index < alias.length(); index++) {
//				char c = alias.charAt(i);
//				Validate.isTrue((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (StringUtils.isSymbol(c)),
//						"数据库别名【%s】命名必须是小写或者小写拼接数字", alias);
//			}
//			for (int index = 0; index < reallyName.length(); index++) {
//				char c = reallyName.charAt(i);
//				Validate.isTrue((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (StringUtils.isSymbol(c)),
//						"数据库真实名【%s】命名必须是小写或者小写拼接数字", reallyName);
//			}
//
//			databaseMeta.getReallyNames().add(reallyName);
//
//			boolean auto = SystemProperties.getBooleanValue("database.autoCreate", true);
//			if (auto) {// 自动创库
//				if (!dataSourcePool.containsKey(Database.HELPDB)) {// 使用mysql自带的数据库用于执行辅助操作，比如创建数据库等功能
//					Properties helpProperties = new Properties();
//					properties.forEach((k, v) -> helpProperties.put(k, v));
//					helpProperties.put(urlKey, properties.getProperty(urlKey).replaceAll(reallyName, Database.HELPDB));
//					try {
//						DataSource helpDataSource = dataSourcePool.computeIfAbsent(Database.HELPDB, k -> {
//							try {
//								return (databaseMeta.getConnectionPool() == Database.ConnectionPool.HIKARI
//										? new HikariDataSource(new HikariConfig(helpProperties))
//										: databaseMeta.getConnectionPool() == Database.ConnectionPool.DRUID
//												? DruidDataSourceFactory.createDataSource(helpProperties)
//												: databaseMeta.getConnectionPool() == Database.ConnectionPool.DBCP2
//														? BasicDataSourceFactory.createDataSource(properties)
//														: null);
//							} catch (Exception e) {
//								throw new RuntimeException(e.getMessage(), e);
//							}
//						});
//						Objects.requireNonNull(helpDataSource);
//						log.trace("create datasource pool [{}.{}.{}]", Database.HELPDB,
//								databaseMeta.getConnectionPool(), helpDataSource);
//					} catch (Exception e) {
//						throw new RuntimeException(e.getMessage(), e);
//					}
//				}
//				if (!existsDatabase(reallyName)) {
//					createDatabaseIfNotExists(reallyName, databaseMeta.getCharset(), databaseMeta.getCollate());
//				}
//			}
//
//			try {
//				DataSource dataSource = dataSourcePool.computeIfAbsent(alias, k -> {
//					try {
//						return (databaseMeta.getConnectionPool() == Database.ConnectionPool.HIKARI
//								? new HikariDataSource(new HikariConfig(properties))
//								: databaseMeta.getConnectionPool() == Database.ConnectionPool.DRUID
//										? DruidDataSourceFactory.createDataSource(properties)
//										: databaseMeta.getConnectionPool() == Database.ConnectionPool.DBCP2
//												? BasicDataSourceFactory.createDataSource(properties)
//												: null);
//					} catch (Exception e) {
//						throw new RuntimeException(e.getMessage(), e);
//					}
//				});
//				Objects.requireNonNull(dataSource);
//				dataSourcePool.computeIfAbsent(reallyName, k -> dataSource);
//				log.trace("create datasource pool [{}.{}.{}]", alias, databaseMeta.getConnectionPool(), dataSource);
//				log.trace("create datasource pool [{}.{}.{}]", reallyName, databaseMeta.getConnectionPool(),
//						dataSource);
//			} catch (Exception e) {
//				throw new RuntimeException("初始化数据库连接池失败:" + entityClass + "," + file, e);
//			}
//		}
//
//		return getDatabaseMeta(entityClass);
//	}

	public static boolean existsDatabase(String realName) {
		AtomicBoolean exists = new AtomicBoolean();
		Connection connection = null;
		try {
			connection = openConnection(Database.HELPDB);
		} catch (Exception e) {

		}
		if (connection == null) {
			try {
				connection = openConnection(realName);
			} catch (Exception e) {

			}
		}
		String sql = String.format(
				"select  information_schema.schemata.schema_name from information_schema.schemata where schema_name='%s'",
				realName);
		BaseDaoImpl.getInstance().executeQuery(connection, sql, (rs) -> {
			try {
				String db = rs.getString(1);
				if (db != null)
					exists.set(db.equals(realName));
			} catch (SQLException e) {
				throw new RuntimeException(e.getMessage() + ":" + sql, e);
			}
		});
		return exists.get();
	}

	public static void createDatabaseIfNotExists(String database, String charset, String collate) {
		BaseDaoImpl.getInstance().execute(Database.HELPDB, String
				.format("create database if not exists %s default charset %s collate %s", database, charset, collate));
	}

	public static String getOnlyReallyName(DatabaseMeta databaseMeta) {
		return CollectionUtils.peekFirst(databaseMeta.getReallyNames());
	}

	public static Map<String, Set<Serializable>> matchedDatabases(DatabaseMeta databaseMeta,

			Iterable<? extends Serializable> ids) {

		//分库
		if (databaseMeta.getCluster() > 1) {
			Map<String, Set<Serializable>> dbIdsMap = Maps.newHashMap();
			ids.forEach(
					id -> dbIdsMap.computeIfAbsent(findDatabaseByPk(databaseMeta, id), k -> Sets.newHashSet()).add(id));
			return dbIdsMap;
		}
	
		return new HashMap<String, Set<Serializable>>() {
			{
				put(getOnlyReallyName(databaseMeta), Sets.newHashSet(ids));
			}
		};
	}

	
	
	
	
	public static String findDatabaseByPk(DatabaseMeta databaseMeta, Serializable id) {
		Validate.isTrue(id != null && databaseMeta.getCluster() > 1);
		int hash = HashUtils.hashCode(id);
		int index = hash % databaseMeta.getCluster();
		for (String db : databaseMeta.getReallyNames()) {
			if (db.endsWith("" + index)) {
				return db;
			}
		}
		throw new NullPointerException();
	}

	static public Class<?> getEntityClass(DatabaseMeta databaseMeta) {
		try {
			return (Class<?>) Class.forName(databaseMeta.getEntityClassName());
		} catch (ClassNotFoundException e) {
			throw new NullPointerException(databaseMeta.getEntityClassName());
		}
	}
}
